/* 
* linux-3.0.8/drivers/net/gt2440_net.c
*/

#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>

#include "gt2440_net.h"


static char version[] = "GT2440 NET Device Driver: CS8900\n";

static unsigned int io;
static char media[8];
static int duplex=-1;


/* data structure */
static const struct net_device_ops net_dev_ops = {
	.ndo_open		= gt2440_net_open,
	.ndo_stop			= gt2440_net_stop,
	.ndo_tx_timeout	= gt2440_net_tx_timeout,
	.ndo_start_xmit 	= gt2440_net_start_xmit,
	.ndo_get_stats		= gt2440_net_get_stats,
	.ndo_set_multicast_list = gt2440_net_set_multicast_list,
	.ndo_set_mac_address 	= gt2440_net_set_mac_address,
#ifdef CONFIG_NET_POLL_CONTROLLER
	.ndo_poll_controller	= gt2440_net_poll_controller,
#endif
	.ndo_change_mtu		= eth_change_mtu,
	.ndo_validate_addr	= eth_validate_addr,
};



/***** help function *****/
static void readwords(unsigned long base_addr, int portno, void *buf, int length)
{
	u8 *buf8 = (u8 *)buf;
	do {
		u16 tmp16;
		tmp16 = readword(base_addr, portno);
		*buf8++ = (u8)tmp16;
		*buf8++ = (u8)(tmp16 >> 8);
	} while (--length);
}

static void writewords(unsigned long base_addr, int portno, void *buf, int length)
{
	u8 *buf8 = (u8 *)buf;
	do {
		u16 tmp16;
		tmp16 = *buf8++;
		tmp16 |= (*buf8++) << 8;
		writeword(base_addr, portno, tmp16);
	} while (--length);
}
static u16 readreg(struct net_device *net_dev, u16 regno)
{
	writeword(net_dev->base_addr, ADD_PORT, regno);
	return readword(net_dev->base_addr, DATA_PORT);
}
static void writereg(struct net_device *net_dev, u16 regno, u16 value)
{
	writeword(net_dev->base_addr, ADD_PORT, regno);
	writeword(net_dev->base_addr, DATA_PORT, value);
}
static void write_irq(struct net_device *net_dev, int chip_type, int irq)
{
	int i;
	if (chip_type == CS8900) {
		i=0;
		writereg(net_dev, PP_CS8900_ISAINT, i);
	} else {
		writereg(net_dev, PP_CS8920_ISAINT, irq);
	}
}
static void gt2440_net_control_dc_dc(struct net_device *net_dev, int on_not_off)
{
	struct gt2440_net_info *info= netdev_priv(net_dev);
	unsigned int selfcontrol;
	int timenow = jiffies;

	selfcontrol = HCB1_ENBL;
	if (((info->adapter_cnf & A_CNF_DC_DC_POLARITY) != 0) ^ on_not_off)
		selfcontrol |= HCB1;
	else
		selfcontrol &= ~HCB1;
	writereg(net_dev, PP_SelfCTL, selfcontrol);
	while (jiffies - timenow < HZ) ;
}
static int gt2440_net_detect_tp(struct net_device *net_dev)
{
	struct gt2440_net_info *info= netdev_priv(net_dev);
	int timenow = jiffies;
	int fdx;

	writereg(net_dev, PP_LineCTL, info->linectl &~ AUI_ONLY);
	gt2440_net_control_dc_dc(net_dev, 0);
	for (timenow = jiffies; jiffies - timenow < 15; )  ;
	if ((readreg(net_dev, PP_LineST) & LINK_OK) == 0)
		return DETECTED_NONE;

	if (info->chip_type == CS8900) {
                switch (info->force & 0xf0) {
                case FORCE_AUTO:
			info->force &= ~FORCE_AUTO;
                        info->force |= FORCE_HALF;
			break;
		case FORCE_HALF:
			break;
                case FORCE_FULL:
			writereg(net_dev, PP_TestCTL, readreg(net_dev, PP_TestCTL) | FDX_8900);
			break;
                }
		fdx = readreg(net_dev, PP_TestCTL) & FDX_8900;
	} else {
		switch (info->force & 0xf0) {
		case FORCE_AUTO:
			info->auto_neg_cnf = AUTO_NEG_ENABLE;
			break;
		case FORCE_HALF:
			info->auto_neg_cnf = 0;
			break;
		case FORCE_FULL:
			info->auto_neg_cnf = RE_NEG_NOW | ALLOW_FDX;
			break;
                }
		writereg(net_dev, PP_AutoNegCTL, info->auto_neg_cnf & AUTO_NEG_MASK);
		if ((info->auto_neg_cnf & AUTO_NEG_BITS) == AUTO_NEG_ENABLE) {
			printk(KERN_INFO "%s: negotiating duplex...\n",net_dev->name);
			while (readreg(net_dev, PP_AutoNegST) & AUTO_NEG_BUSY) {
				if (jiffies - timenow > 4000) {
					printk(KERN_ERR "**** Full / half duplex auto-negotiation timed out ****\n");
					break;
				}
			}
		}
		fdx = readreg(net_dev, PP_AutoNegST) & FDX_ACTIVE;
	}
	if (fdx)
		return DETECTED_RJ45F;
	else
		return DETECTED_RJ45H;
}
static int gt2440_send_test_pkt(struct net_device *net_dev)
{
	char test_packet[] = { 0,0,0,0,0,0, 0,0,0,0,0,0,
				 0, 46, /* A 46 in network order */
				 0, 0, /* DSAP=0 & SSAP=0 fields */
				 0xf3, 0 /* Control (Test Req + P bit set) */ };
	long timenow = jiffies;

	writereg(net_dev, PP_LineCTL, readreg(net_dev, PP_LineCTL) | SERIAL_TX_ON);
	memcpy(test_packet,          net_dev->dev_addr, ETH_ALEN);
	memcpy(test_packet+ETH_ALEN, net_dev->dev_addr, ETH_ALEN);
        writeword(net_dev->base_addr, TX_CMD_PORT, TX_AFTER_ALL);
        writeword(net_dev->base_addr, TX_LEN_PORT, ETH_ZLEN);
	while (jiffies - timenow < 5)
		if (readreg(net_dev, PP_BusST) & READY_FOR_TX_NOW)
			break;
	if (jiffies - timenow >= 5)
		return 0;
	writewords(net_dev->base_addr, TX_FRAME_PORT,test_packet,(ETH_ZLEN+1) >>1);
	for (timenow = jiffies; jiffies - timenow < 3; )  ;
        if ((readreg(net_dev, PP_TxEvent) & TX_SEND_OK_BITS) == TX_OK) {
                return 1;
        }
	return 0;
}

static int gt2440_net_detect_aui(struct net_device *net_dev)
{
	struct gt2440_net_info *info = netdev_priv(net_dev);

	gt2440_net_control_dc_dc(net_dev, 0);
	writereg(net_dev, PP_LineCTL, (info->linectl &~ AUTO_AUI_10BASET) | AUI_ONLY);
	if (gt2440_send_test_pkt(net_dev))
		return DETECTED_AUI;
	else
		return DETECTED_NONE;
}

static int gt2440_net_detect_bnc(struct net_device *net_dev)
{
	struct gt2440_net_info *info = netdev_priv(net_dev);
	gt2440_net_control_dc_dc(net_dev, 1);
	writereg(net_dev, PP_LineCTL, (info->linectl &~ AUTO_AUI_10BASET) | AUI_ONLY);
	if (gt2440_send_test_pkt(net_dev))
		return DETECTED_BNC;
	else
		return DETECTED_NONE;
}


/***** net device ops function *****/
static int gt2440_net_open(struct net_device *net_dev)
{	dprintk("gt2440_net_open()\n");
	struct gt2440_net_info *info = netdev_priv(net_dev);
	int result = 0;
	int i;
	int ret;

	writereg(net_dev, PP_BusCTL, readreg(net_dev, PP_BusCTL)|ENABLE_IRQ );
	write_irq(net_dev, info->chip_type, net_dev->irq);
	ret = request_irq(net_dev->irq, gt2440_net_interrupt, 
		IRQF_TRIGGER_RISING, net_dev->name, net_dev);
	if (ret) {
		printk(KERN_ERR "cs89x0: request_irq(%d) failed\n", net_dev->irq);
		goto bad_out;
	}

	for (i=0; i < ETH_ALEN/2; i++)
		writereg(net_dev, PP_IA+i*2, net_dev->dev_addr[i*2] | (net_dev->dev_addr[i*2+1] << 8));
	writereg(net_dev, PP_BusCTL, MEMORY_ON);

	if ((info->adapter_cnf & A_CNF_EXTND_10B_2) && (info->adapter_cnf & A_CNF_LOW_RX_SQUELCH))
                info->linectl = LOW_RX_SQUELCH;
	else
                info->linectl = 0;
	switch(info->adapter_cnf & A_CNF_MEDIA_TYPE) {
	case A_CNF_MEDIA_10B_T: result = info->adapter_cnf & A_CNF_10B_T; break;
	case A_CNF_MEDIA_AUI:   result = info->adapter_cnf & A_CNF_AUI; break;
	case A_CNF_MEDIA_10B_2: result = info->adapter_cnf & A_CNF_10B_2; break;
        default: result = info->adapter_cnf & (A_CNF_10B_T | A_CNF_AUI | A_CNF_10B_2);
        }

	result = A_CNF_10B_T;
        if (!result) {
                printk(KERN_ERR "%s: EEPROM is configured for unavailable media\n", net_dev->name);
release_dma:
                writereg(net_dev, PP_LineCTL, readreg(net_dev, PP_LineCTL) & ~(SERIAL_TX_ON | SERIAL_RX_ON));
                free_irq(net_dev->irq, net_dev);
		ret = -EAGAIN;
		goto bad_out;
	}
	switch(info->adapter_cnf & A_CNF_MEDIA_TYPE) {
	case A_CNF_MEDIA_10B_T:
                result = gt2440_net_detect_tp(net_dev);
                if (result==DETECTED_NONE) {
                        printk(KERN_WARNING "%s: 10Base-T (RJ-45) has no cable\n", net_dev->name);
                        if (info->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
                                result = DETECTED_RJ45H; /* Yes! I don't care if I see a link pulse */
                }
		break;
	case A_CNF_MEDIA_AUI:
                result = gt2440_net_detect_aui(net_dev);
                if (result==DETECTED_NONE) {
                        printk(KERN_WARNING "%s: 10Base-5 (AUI) has no cable\n", net_dev->name);
                        if (info->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
                                result = DETECTED_AUI; /* Yes! I don't care if I see a carrrier */
                }
		break;
	case A_CNF_MEDIA_10B_2:
                result = gt2440_net_detect_bnc(net_dev);
                if (result==DETECTED_NONE) {
                        printk(KERN_WARNING "%s: 10Base-2 (BNC) has no cable\n", net_dev->name);
                        if (info->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
                                result = DETECTED_BNC; /* Yes! I don't care if I can xmit a packet */
                }
		break;
	case A_CNF_MEDIA_AUTO:
		writereg(net_dev, PP_LineCTL, info->linectl | AUTO_AUI_10BASET);
		if (info->adapter_cnf & A_CNF_10B_T)
			if ((result = gt2440_net_detect_tp(net_dev)) != DETECTED_NONE)
				break;
		if (info->adapter_cnf & A_CNF_AUI)
			if ((result = gt2440_net_detect_aui(net_dev)) != DETECTED_NONE)
				break;
		if (info->adapter_cnf & A_CNF_10B_2)
			if ((result = gt2440_net_detect_bnc(net_dev)) != DETECTED_NONE)
				break;
		printk(KERN_ERR "%s: no media detected\n", net_dev->name);
		goto release_dma;
	}
	switch(result) {
	case DETECTED_NONE:
		printk(KERN_ERR "%s: no network cable attached to configured media\n", net_dev->name);
		goto release_dma;
	case DETECTED_RJ45H:
		printk(KERN_INFO "%s: using half-duplex 10Base-T (RJ-45)\n", net_dev->name);
		break;
	case DETECTED_RJ45F:
		printk(KERN_INFO "%s: using full-duplex 10Base-T (RJ-45)\n", net_dev->name);
		break;
	case DETECTED_AUI:
		printk(KERN_INFO "%s: using 10Base-5 (AUI)\n", net_dev->name);
		break;
	case DETECTED_BNC:
		printk(KERN_INFO "%s: using 10Base-2 (BNC)\n", net_dev->name);
		break;
	}
	writereg(net_dev, PP_LineCTL, readreg(net_dev, PP_LineCTL) | SERIAL_RX_ON | SERIAL_TX_ON);
	info->rx_mode = 0;
	writereg(net_dev, PP_RxCTL, DEF_RX_ACCEPT);
	info->curr_rx_cfg = RX_OK_ENBL | RX_CRC_ERROR_ENBL;
	if (info->isa_config & STREAM_TRANSFER)
		info->curr_rx_cfg |= RX_STREAM_ENBL;
	writereg(net_dev, PP_RxCFG, info->curr_rx_cfg);
	writereg(net_dev, PP_TxCFG, TX_LOST_CRS_ENBL | TX_SQE_ERROR_ENBL | TX_OK_ENBL |
		TX_LATE_COL_ENBL | TX_JBR_ENBL | TX_ANY_COL_ENBL | TX_16_COL_ENBL);
	writereg(net_dev, PP_BufCFG, READY_FOR_TX_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL |
		TX_COL_COUNT_OVRFLOW_ENBL | TX_UNDERRUN_ENBL);
	writereg(net_dev, PP_BusCTL, ENABLE_IRQ | (net_dev->mem_start?MEMORY_ON : 0));
        netif_start_queue(net_dev);
	return 0;
bad_out:
	return ret;
}
static int gt2440_net_stop(struct net_device *net_dev)
{	dprintk("gt2440_net_stop()\n");
	netif_stop_queue(net_dev);
	writereg(net_dev, PP_RxCFG, 0);
	writereg(net_dev, PP_TxCFG, 0);
	writereg(net_dev, PP_BufCFG, 0);
	writereg(net_dev, PP_BusCTL, 0);
	free_irq(net_dev->irq, net_dev);
	return 0;
}
static void gt2440_net_tx_timeout(struct net_device *dev)
{	//dprintk("gt2440_net_tx_timeout()\n");
	netif_wake_queue(dev);
}

static netdev_tx_t gt2440_net_start_xmit(struct sk_buff *skb,struct net_device *net_dev)
{	//dprintk("gt2440_net_start_xmit()\n");
	struct gt2440_net_info *info= netdev_priv(net_dev);
	unsigned long flags;
	
	spin_lock_irqsave(&info->lock, flags);
	netif_stop_queue(net_dev);
	writeword(net_dev->base_addr, TX_CMD_PORT, info->send_cmd);
	writeword(net_dev->base_addr, TX_LEN_PORT, skb->len);
	if ((readreg(net_dev, PP_BusST) & READY_FOR_TX_NOW) == 0) {
		spin_unlock_irqrestore(&info->lock, flags);
		return NETDEV_TX_BUSY;
	}
	writewords(net_dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);
	spin_unlock_irqrestore(&info->lock, flags);
	net_dev->stats.tx_bytes += skb->len;
	dev_kfree_skb (skb);
	return NETDEV_TX_OK;
}
static struct net_device_stats *gt2440_net_get_stats(struct net_device *net_dev)
{	//dprintk("gt2440_net_get_stats()\n");
	struct gt2440_net_info *info= netdev_priv(net_dev);
	unsigned long flags;

	spin_lock_irqsave(&info->lock, flags);
	net_dev->stats.rx_missed_errors += (readreg(net_dev, PP_RxMiss) >> 6);
	net_dev->stats.collisions += (readreg(net_dev, PP_TxCol) >> 6);
	spin_unlock_irqrestore(&info->lock, flags);
	return &net_dev->stats;
}
static void gt2440_net_set_multicast_list(struct net_device *net_dev)
{	//dprintk("gt2440_net_set_multicast_list()\n");
	struct gt2440_net_info *info= netdev_priv(net_dev);
	unsigned long flags;

	spin_lock_irqsave(&info->lock, flags);
	if(net_dev->flags&IFF_PROMISC)
	{
		info->rx_mode = RX_ALL_ACCEPT;
	}
	else if ((net_dev->flags & IFF_ALLMULTI) || !netdev_mc_empty(net_dev))
	{
		/* The multicast-accept list is initialized to accept-all, and we
		   rely on higher-level filtering for now. */
		info->rx_mode = RX_MULTCAST_ACCEPT;
	}
	else
		info->rx_mode = 0;

	writereg(net_dev, PP_RxCTL, DEF_RX_ACCEPT | info->rx_mode);

	/* in promiscuous mode, we accept errored packets, so we have to enable interrupts on them also */
	writereg(net_dev, PP_RxCFG, info->curr_rx_cfg |
	     (info->rx_mode == RX_ALL_ACCEPT? (RX_CRC_ERROR_ENBL|RX_RUNT_ENBL|RX_EXTRA_DATA_ENBL) : 0));
	spin_unlock_irqrestore(&info->lock, flags);
}


static int gt2440_net_set_mac_address(struct net_device *net_dev, void *p)
{	//dprintk("gt2440_net_set_mac_address()\n");
	int i;
	struct sockaddr *addr = p;

	if (netif_running(net_dev))
		return -EBUSY;
	memcpy(net_dev->dev_addr, addr->sa_data, net_dev->addr_len);
	for (i=0; i < ETH_ALEN/2; i++)
		writereg(net_dev, PP_IA+i*2, net_dev->dev_addr[i*2] | (net_dev->dev_addr[i*2+1] << 8));
	return 0;
}
#ifdef CONFIG_NET_POLL_CONTROLLER
static void gt2440_net_poll_controller(struct net_device *net_dev)
{
	disable_irq(net_dev->irq);
	gt2440_net_interrupt(net_dev->irq, net_dev);
	enable_irq(net_dev->irq);
}
#endif


/***** tx and rx interrupt *****/
static void gt2440_net_count_rx_errors(int status, struct net_device *net_dev)
{
	net_dev->stats.rx_errors++;
	if (status & RX_RUNT)
		net_dev->stats.rx_length_errors++;
	if (status & RX_EXTRA_DATA)
		net_dev->stats.rx_length_errors++;
	if ((status & RX_CRC_ERROR) && !(status & (RX_EXTRA_DATA|RX_RUNT)))
		net_dev->stats.rx_crc_errors++;
	if (status & RX_DRIBBLE)
		net_dev->stats.rx_frame_errors++;
}
static void gt2440_net_net_rx(struct net_device *net_dev)
{
	struct sk_buff *skb;
	int status, length;

	int ioaddr = net_dev->base_addr;
	status = readword(ioaddr, RX_FRAME_PORT);
	length = readword(ioaddr, RX_FRAME_PORT);
	if ((status & RX_OK) == 0) {
		gt2440_net_count_rx_errors(status, net_dev);
		return;
	}
	skb = dev_alloc_skb(length + 2);
	if (skb == NULL) {
		net_dev->stats.rx_dropped++;
		return;
	}
	skb_reserve(skb, 2);	/* longword align L3 header */
	readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1);
	if (length & 1)
		skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT);
        skb->protocol=eth_type_trans(skb,net_dev);
	netif_rx(skb);
	net_dev->stats.rx_packets++;
	net_dev->stats.rx_bytes += length;
}
static irqreturn_t gt2440_net_interrupt(int irq, void *dev_id)
{	//dprintk("gt2440_net_interrupt()\n");
	struct net_device *net_dev = dev_id;
	struct gt2440_net_info *info;
	int ioaddr, status;
 	int handled = 0;

	ioaddr = net_dev->base_addr;
	info = netdev_priv(net_dev);
	while ((status = readword(net_dev->base_addr, ISQ_PORT))) {
		handled = 1;
		switch(status & ISQ_EVENT_MASK) {
		case ISQ_RECEIVER_EVENT:
			gt2440_net_net_rx(net_dev);
			break;
		case ISQ_TRANSMITTER_EVENT:
			net_dev->stats.tx_packets++;
			netif_wake_queue(net_dev);
			if ((status & (	TX_OK |
					TX_LOST_CRS |
					TX_SQE_ERROR |
					TX_LATE_COL |
					TX_16_COL)) != TX_OK) {
				if ((status & TX_OK) == 0)
					net_dev->stats.tx_errors++;
				if (status & TX_LOST_CRS)
					net_dev->stats.tx_carrier_errors++;
				if (status & TX_SQE_ERROR)
					net_dev->stats.tx_heartbeat_errors++;
				if (status & TX_LATE_COL)
					net_dev->stats.tx_window_errors++;
				if (status & TX_16_COL)
					net_dev->stats.tx_aborted_errors++;
			}
			break;
		case ISQ_BUFFER_EVENT:
			if (status & READY_FOR_TX) {
				netif_wake_queue(net_dev);	
			}
			if (status & TX_UNDERRUN) {
                                info->send_underrun++;
                                if (info->send_underrun == 3) info->send_cmd = TX_AFTER_381;
                                else if (info->send_underrun == 6) info->send_cmd = TX_AFTER_ALL;
				netif_wake_queue(net_dev);	
                        }
			break;
		case ISQ_RX_MISS_EVENT:
			net_dev->stats.rx_missed_errors += (status >> 6);
			break;
		case ISQ_TX_COL_EVENT:
			net_dev->stats.collisions += (status >> 6);
			break;
		}
	}
	return IRQ_RETVAL(handled);
}



/***** probe and remove function *****/
static void gt2440_net_init_bwscon(struct gt2440_net_info *info)
{	
	info->bwscon = *((volatile unsigned int *)GT2440_BWSCON);
	info->bankcon3 = *((volatile unsigned int *)GT2440_BANKCON3);
	
	*((volatile unsigned int *)GT2440_BWSCON) = (info->bwscon & ~(3<<12)) | 
		GT2440_BWSCON_DW3_16 | 
		GT2440_BWSCON_WS3 | 
		GT2440_BWSCON_ST3;
	*((volatile unsigned int *)GT2440_BANKCON3) = 0x1f7c;
}
static void gt2440_net_deinit_bwscon(struct gt2440_net_info *info){
	*((volatile unsigned int *)GT2440_BWSCON) = info->bwscon;
	*((volatile unsigned int *)GT2440_BANKCON3) = info->bankcon3;
	
}

static int gt2440_net_wait_eeprom_ready(struct net_device *net_dev)
{	//dprintk("gt2440_net_wait_eeprom_ready()\n");
	int timeout = jiffies;
	while(readreg(net_dev, PP_SelfST) & SI_BUSY)
		if (jiffies - timeout >= 40)
			return -1;
	return 0;
}
static int gt2440_net_get_eeprom_data(struct net_device *net_dev,
									int off, int len, int *buffer)
{	//dprintk("gt2440_net_get_eeprom_data()\n");
	int i;
	for (i = 0; i < len; i++) {
		if (gt2440_net_wait_eeprom_ready(net_dev) < 0) return -1;
		writereg(net_dev, PP_EECMD, (off + i) | EEPROM_READ_CMD);
		if (gt2440_net_wait_eeprom_ready(net_dev) < 0) return -1;
		buffer[i] = readreg(net_dev, PP_EEData);
	}
        return 0;
}
static int  gt2440_net_get_eeprom_cksum(int off, int len, int *buffer)
{	//dprintk("gt2440_net_get_eeprom_cksum()\n");
	int i, cksum;
	cksum = 0;
	for (i = 0; i < len; i++)
		cksum += buffer[i];
	cksum &= 0xffff;
	if (cksum == 0)
		return 0;
	return -1;
}
static void gt2440_net_reset_chip(struct net_device *net_dev)
{	dprintk("gt2440_net_reset_chip()\n");
	struct gt2440_net_info *info = netdev_priv(net_dev);
	int ioaddr = net_dev->base_addr;
	int reset_start_time;

	writereg(net_dev, PP_SelfCTL, readreg(net_dev, PP_SelfCTL) | POWER_ON_RESET);
	msleep(30);
	if (info->chip_type != CS8900) {
		writeword(ioaddr, ADD_PORT, PP_CS8920_ISAINT);
		outb(net_dev->irq, ioaddr + DATA_PORT);
		outb(0,      ioaddr + DATA_PORT + 1);
		writeword(ioaddr, ADD_PORT, PP_CS8920_ISAMemB);
		outb((net_dev->mem_start >> 16) & 0xff, ioaddr + DATA_PORT);
		outb((net_dev->mem_start >> 8) & 0xff,   ioaddr + DATA_PORT + 1);
	}
	reset_start_time = jiffies;
	while( (readreg(net_dev, PP_SelfST) & INIT_DONE) == 0 && jiffies - reset_start_time < 2)
		;
}

static int gt2440_net_probe(struct platform_device *pdev)
{	dprintk("gt2440_net_probe(id=%d)\n", pdev->id);
	struct net_device *net_dev ;
	struct gt2440_net_info *info;
	struct resource *res;
	unsigned int rev_type = 0;
	unsigned version_printed;

	int eeprom_buff[CHKSUM_LEN];
	int size;
	int ret = 0;
	int tmp;
	int i;
	
	net_dev = alloc_etherdev(sizeof(struct gt2440_net_info));
	if (!net_dev){
		dev_err(&pdev->dev,"failed to allocate struct gt2440_net_info{} for net \n");
		return -ENOMEM;
	}
		
	SET_NETDEV_DEV(net_dev, &pdev->dev);
	info = netdev_priv(net_dev);
	info->net_dev = net_dev;
	info->dev = &pdev->dev;
	platform_set_drvdata(pdev, net_dev);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory registers\n");
		ret = -ENXIO;
		goto	exit_1;
	}
	size = resource_size(res);
	info->res = request_mem_region(res->start, size, pdev->name);
	if (info->res == NULL) {
		dev_err(&pdev->dev, "failed to request memory region\n");
		ret = -ENOENT;
		goto exit_1;
	}
	info->regs = ioremap(res->start, size);
	if (info->regs == NULL) {
		dev_err(&pdev->dev, "failed to ioremap memory region\n");
		ret = -EIO;
		goto exit_2;
	}

	info->irq = platform_get_irq(pdev, 0);
	if (info->irq < 0) {
		dev_err(&pdev->dev, "failed to get irq for net device\n");
		ret = -ENOENT;
		goto exit_3;
	}
	net_dev->base_addr = (unsigned long)info->regs + 0x300;
	net_dev->irq	= info->irq;
	
	io = (unsigned int)info->regs + 0x300;
	net_dev->dev_addr[0] = 0x08;
	net_dev->dev_addr[1] = 0x89;
	net_dev->dev_addr[2] = 0x89;
	net_dev->dev_addr[3] = 0x89;
	net_dev->dev_addr[4] = 0x89;
	net_dev->dev_addr[5] = 0x89;

	gt2440_net_init_bwscon(info);

	spin_lock_init(&info->lock);
        if (!strcmp(media, "rj45"))
		info->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T;
	else if (!strcmp(media, "aui"))
		info->adapter_cnf = A_CNF_MEDIA_AUI   | A_CNF_AUI;
	else if (!strcmp(media, "bnc"))
		info->adapter_cnf = A_CNF_MEDIA_10B_2 | A_CNF_10B_2;
	else
		info->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T;
        if (duplex==-1)
		info->auto_neg_cnf = AUTO_NEG_ENABLE;
	writeword(net_dev->base_addr, ADD_PORT, PP_ChipID);
	tmp = readword(net_dev->base_addr, DATA_PORT);
	if (tmp != CHIP_EISA_ID_SIG) {
		dev_err(&pdev->dev, "failed to incorrect signature at %x[%x]: 0x%x!="
			CHIP_EISA_ID_SIG_STR "\n",net_dev->base_addr, DATA_PORT, tmp);
  		ret = -ENODEV;
  		goto exit_3;
	}
	rev_type = readreg(net_dev, PRODUCT_ID_ADD);
	info->chip_type = rev_type &~ REVISON_BITS;
	info->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A';
	info->send_cmd = TX_AFTER_381;
	if (info->chip_type == CS8900 && info->chip_revision >= 'F')
		info->send_cmd = TX_NOW;
	if (info->chip_type != CS8900 && info->chip_revision >= 'C')
		info->send_cmd = TX_NOW;
	printk(KERN_INFO"%s: cs89%c0%s rev %c found at %#3lx \n",
	       net_dev->name,
	       info->chip_type==CS8900?'0':'2',
	       info->chip_type==CS8920M?"M":"",
	       info->chip_revision,
	       net_dev->base_addr);	
	gt2440_net_reset_chip(net_dev);

    if ((readreg(net_dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) ==
	      (EEPROM_OK|EEPROM_PRESENT)) {
		for (i=0; i < ETH_ALEN/2; i++) {
	                unsigned int Addr;
			Addr = readreg(net_dev, PP_IA+i*2);
		        net_dev->dev_addr[i*2] = Addr & 0xFF;
		        net_dev->dev_addr[i*2+1] = Addr >> 8;
		}
		info->adapter_cnf = 0;
		i = readreg(net_dev, PP_LineCTL);
		if ((i & (HCB1 | HCB1_ENBL)) ==  (HCB1 | HCB1_ENBL))
			info->adapter_cnf |= A_CNF_DC_DC_POLARITY;
		if ((i & LOW_RX_SQUELCH) == LOW_RX_SQUELCH)
			info->adapter_cnf |= A_CNF_EXTND_10B_2 | A_CNF_LOW_RX_SQUELCH;
		if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == 0)
			info->adapter_cnf |=  A_CNF_10B_T | A_CNF_MEDIA_10B_T;
		if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUI_ONLY)
			info->adapter_cnf |=  A_CNF_AUI | A_CNF_MEDIA_AUI;
		if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUTO_AUI_10BASET)
			info->adapter_cnf |=  A_CNF_AUI | A_CNF_10B_T |
			A_CNF_MEDIA_AUI | A_CNF_MEDIA_10B_T | A_CNF_MEDIA_AUTO;
		if (info->chip_type == CS8900)
			info->isa_config = readreg(net_dev, PP_CS8900_ISAINT) & INT_NO_MASK;
	}
	dprintk("\n");
	if((readreg(net_dev, PP_SelfST) & EEPROM_PRESENT) == 0) /*go to here */
		printk(KERN_WARNING "cs89x0: No EEPROM, relying on command line....\n");
	else if(gt2440_net_get_eeprom_data(net_dev, START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff)< 0){
		printk(KERN_WARNING "\ncs89x0: EEPROM read failed, relying on command line.\n");
    } else if(gt2440_net_get_eeprom_cksum(START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff) < 0){
		if((readreg(net_dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) !=
		    (EEPROM_OK|EEPROM_PRESENT))
             printk(KERN_WARNING "cs89x0: Extended EEPROM checksum bad and no Cirrus EEPROM, relying on command line\n");
    }else{
         if (!info->auto_neg_cnf) info->auto_neg_cnf = eeprom_buff[AUTO_NEG_CNF_OFFSET/2];
         if (!info->adapter_cnf) info->adapter_cnf = eeprom_buff[ADAPTER_CNF_OFFSET/2];
         info->isa_config = eeprom_buff[ISA_CNF_OFFSET/2];
         net_dev->mem_start = eeprom_buff[PACKET_PAGE_OFFSET/2] << 8;
         for (i = 0; i < ETH_ALEN/2; i++) {
                    net_dev->dev_addr[i*2] = eeprom_buff[i];
                    net_dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
         }
    }

    {
		int count = 0;
		if (info->force & FORCE_RJ45){info->adapter_cnf |= A_CNF_10B_T; count++; }
		if (info->force & FORCE_AUI) 	{info->adapter_cnf |= A_CNF_AUI; count++; }
		if (info->force & FORCE_BNC)	{info->adapter_cnf |= A_CNF_10B_2; count++; }
		if (count > 1)				{info->adapter_cnf |= A_CNF_MEDIA_AUTO; }
		else if (info->force & FORCE_RJ45){info->adapter_cnf |= A_CNF_MEDIA_10B_T; }
		else if (info->force & FORCE_AUI)	{info->adapter_cnf |= A_CNF_MEDIA_AUI; }
		else if (info->force & FORCE_BNC)	{info->adapter_cnf |= A_CNF_MEDIA_10B_2; }
    }

	dprintk("cs89x0 media %s%s%s",
	       (info->adapter_cnf & A_CNF_10B_T)?"RJ-45,":"",
	       (info->adapter_cnf & A_CNF_AUI)?"AUI,":"",
	       (info->adapter_cnf & A_CNF_10B_2)?"BNC,":"");
	info->irq_map = 0xffff;
	if (info->chip_type != CS8900 &&
		(i = readreg(net_dev, PP_CS8920_ISAINT) & 0xff,
		 (i != 0 && i < CS8920_NO_INTS))) {
		if (!net_dev->irq)
			net_dev->irq = i;
	} else {
		i = info->isa_config & INT_NO_MASK;
		if (info->chip_type == CS8900) {
			 i = info->irq;
			info->irq_map = CS8900_IRQ_MAP; 
		} else {
			int irq_map_buff[IRQ_MAP_LEN/2];
			if (gt2440_net_get_eeprom_data(net_dev, IRQ_MAP_EEPROM_DATA,
					    IRQ_MAP_LEN/2,
					    irq_map_buff) >= 0) {
				if ((irq_map_buff[0] & 0xff) == PNP_IRQ_FRMT)
					info->irq_map = (irq_map_buff[0]>>8) | (irq_map_buff[1] << 8);
			}
		}
		if (!net_dev->irq)
			net_dev->irq = i;
	}
	dprintk(" IRQ %d", net_dev->irq);
	printk(", programmed I/O");
	printk(", MAC %pM\n", net_dev->dev_addr);
	net_dev->netdev_ops	= &net_dev_ops;
	net_dev->watchdog_timeo = HZ;
	ret = register_netdev(net_dev);
	if (ret){
		dev_err(&pdev->dev, "failed to register net device: %d\n", ret);
		goto exit_4;
	}	
	return 0;

exit_4:
	writeword(net_dev->base_addr, ADD_PORT, PP_ChipID);
exit_3:
	iounmap(info->regs);
	info->regs = NULL;
exit_2:
	release_mem_region(info->res->start, resource_size(info->res));
	info->res = NULL;
exit_1:
	free_netdev(net_dev);
	platform_set_drvdata(pdev, NULL);
	
	return ret;
}

static int gt2440_net_remove(struct platform_device *pdev)
{	dprintk("gt2440_net_remove(id=%d)\n", pdev->id);
	
	struct net_device *net_dev = platform_get_drvdata(pdev);
	struct gt2440_net_info *info;
	
	if (net_dev == NULL)
		return 0;

	info = netdev_priv(net_dev);
	unregister_netdev(net_dev);
	writeword(net_dev->base_addr, ADD_PORT, PP_ChipID);
	if(info->regs != NULL){
		iounmap(info->regs);
		info->regs = NULL;
	}
	if (info->res != NULL) {
		release_mem_region(info->res->start, resource_size(info->res));
		info->res = NULL;
	}
	gt2440_net_deinit_bwscon(info);
	free_netdev(net_dev);
	platform_set_drvdata(pdev, NULL);
	return 0;
}


/***** gt2440 board probe and remove function *****/
static int gt2440_board_net_probe(struct platform_device *pdev)
{	dprintk("gt2440_board_net_probe(id=%d)\n", pdev->id);
	//return 0;
	return gt2440_net_probe(pdev);
}
static int gt2440_board_net_remove(struct platform_device *pdev)
{	dprintk("gt2440_board_net_remove(id=%d)\n", pdev->id);
	//return 0;
	return gt2440_net_remove(pdev);
}

static struct platform_driver gt2440_net_platform_driver = {
	.probe		= gt2440_board_net_probe,
	.remove		= gt2440_board_net_remove,
	.driver		= {
		.name	= PLAT_DEVICE_NAME, /* "gt2440-net" */
		.owner	= THIS_MODULE,
	},
};


/***** init and exit ****/
static int __init gt2440_net_init(void)
{	dprintk("gt2440_net_init()\n");
	return platform_driver_register(&gt2440_net_platform_driver);
}

static void __exit gt2440_net_exit(void)
{	dprintk("gt2440_net_exit()\n");
	platform_driver_unregister(&gt2440_net_platform_driver);
}

module_init(gt2440_net_init);
module_exit(gt2440_net_exit);

MODULE_DESCRIPTION("GT2440 NET Device Driver");
MODULE_AUTHOR("Liguang13579<1659890447@qq.com>");
MODULE_LICENSE("GPL v2");

